/*
 * Copyright 2004-2005 The Apache Software Foundation or its licensors,
 *                     as applicable.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.classloader;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import javax.jcr.Credentials;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import junit.framework.TestCase;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jackrabbit.core.RepositoryImpl;
import org.apache.jackrabbit.core.config.RepositoryConfig;
import org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory;

import com.sun.corba.se.ActivationIDL.Repository;
import com.sun.org.omg.CORBA.ExcDescriptionSeqHelper;

/**
 * The <code>ClassLoaderTestBase</code> TODO
 *
 * @author fmeschbe
 * @version $Rev:$, $Date$
 */
public class ClassLoaderTestBase extends TestCase {

    /** Logger for test cases */
    protected static final Log log =
        LogFactory.getLog("org.apache.jackrabbit.classloader.test");

    protected static final String WORKSPACE = "default";
    protected static final String USER = "admin";

    protected static final String PROVIDER_URL = "ClassLoader";
    protected static final String REPOSITORY_NAME = "ClassLoaderRepository";

    protected RepositoryImpl repository;
    protected Session session;

    private Set createdItems = new HashSet();

    public ClassLoaderTestBase() {
        super();
    }

    public ClassLoaderTestBase(String name) {
        super(name);
    }

    protected void setUp() throws Exception {
        super.setUp();

        if (!"repositoryStart".equals(getName())) {
            Context ctx = getInitialContext();
            repository = (RepositoryImpl) ctx.lookup(REPOSITORY_NAME);

            Credentials creds = new SimpleCredentials(USER, USER.toCharArray());
            session = repository.login(creds, WORKSPACE);
        }
    }

    public void repositoryStart() throws Exception {
        InputStream config = getClass().getResourceAsStream("/repository.xml");
        String home = new File("cltest").getAbsolutePath();
        RepositoryConfig rc = RepositoryConfig.create(config, home);
        RepositoryImpl repository = RepositoryImpl.create(rc);

        try {
            Context ctx = getInitialContext();
            ctx.bind(REPOSITORY_NAME, repository);
        } catch (NamingException ne) {
            repository.shutdown();
            throw ne;
        }
    }

    public void repositoryStop() throws Exception {
        // this is special, logout here and clean repository
        disconnect();

        if (repository != null) {
            repository.shutdown();
            repository = null;
        }

        Context ctx = getInitialContext();
        ctx.unbind(REPOSITORY_NAME);
    }

    protected void tearDown() throws Exception {
        disconnect();
        repository = null;
        super.tearDown();
    }

    private Context getInitialContext() throws NamingException {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
            DummyInitialContextFactory.class.getName());
        env.put(Context.PROVIDER_URL, PROVIDER_URL);

        return new InitialContext(env);
    }

    private void disconnect() {
        if (session != null) {
            clearRepository(session);
            session.logout();
            session = null;
        }
    }

    //---------- RepositoryLoader ----------------------------------------------

    protected void loadRepository(Session session, InputStream ins) {
        if (ins == null) {
            ins = getClass().getResourceAsStream("/preload.properties");
            if (ins == null) {
                log.warn("Cannot find preload properties /preload.properties");
                return;
            }
        }

        List keys = new ArrayList();
        Properties props = new Properties();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(ins));
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }

                // cut off line comment
                int comment = line.indexOf('#');
                if (comment >= 0) {
                    line = line.substring(0, comment);
                }

                // trim leading and trailing whitespace
                line = line.trim();

                // ignore line of empty
                if (line.length() == 0) {
                    continue;
                }

                int sep = line.indexOf('=');
                if (sep < 0) {
                    continue;
                }

                String key = line.substring(0, sep).trim();

                StringBuffer buf = new StringBuffer(line.substring(sep+1).trim());

                while (line.endsWith("\\")) {
                    // cut off last back slash
                    buf.setLength(buf.length()-1);

                    line = reader.readLine();
                    if (line == null) {
                        break;
                    }

                    buf.append(line);
                }

                key = loadConvert(key);
                String value = loadConvert(buf.toString());

                keys.add(key);
                props.setProperty(key, value);
            }
        } catch (IOException ioe) {
            // ignore
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ignore) {}
            }

            try {
                ins.close();
            } catch (IOException ignore) {}
        }

        for (Iterator ki=keys.iterator(); ki.hasNext(); ) {
            String path = (String) ki.next();
            String config = props.getProperty(path);
            StringTokenizer tokener = new StringTokenizer(config, ",");
            if (!tokener.hasMoreTokens()) {
                continue;
            }

            Node parent = null;
            try {
                parent = getParent(session, path);
            } catch (RepositoryException re) {
                log.warn("Cannot get parent of " + path, re);
            }

            if (parent == null) {
                continue;
            }

            try {
                String type = tokener.nextToken();
                if ("n".equalsIgnoreCase(type)) {
                    loadNode(parent, getName(path), tokener);
                    createdItems.add(path);
                } else if ("p".equalsIgnoreCase(type)) {
                    loadProperty(parent, getName(path), tokener);
                }
            } catch (RepositoryException re) {
                log.warn("Cannot create item " + path, re);
            }
        }

        try {
            if (session.hasPendingChanges()) {
                session.save();
            }
        } catch (RepositoryException re) {
            log.warn("Cannot save session", re);
        } finally {
            try {
                if (session.hasPendingChanges()) {
                    session.refresh(false);
                }
            } catch (RepositoryException re) {
                log.warn("Cannot even refresh the session");
            }
        }
    }

    protected void clearRepository(Session session) {
        for (Iterator ii=createdItems.iterator(); ii.hasNext(); ) {
            String path = (String) ii.next();
            try {
                if (!session.itemExists(path)) {
                    continue;
                }

                session.getItem(path).remove();
            } catch (RepositoryException re) {
                log.info("Cannot remove Item " + path + ": " + re);
            }
        }

        try {
            session.save();
        } catch (RepositoryException re) {
            log.warn("Cannot save removals", re);
        }

        createdItems.clear();
    }

    private void loadNode(Node parent, String name,
            StringTokenizer config) throws RepositoryException {

        // node type
        String primaryType;
        if (config.hasMoreTokens()) {
            primaryType = config.nextToken();
        } else {
            primaryType = "nt:unstructured";
        }

        Node node = parent.addNode(name, primaryType);

        // content URL
        if (config.hasMoreTokens()) {
            String urlString = config.nextToken();
            try {
                URL url;
                if (urlString.startsWith("classpath:")) {
                    urlString = urlString.substring("classpath:".length());
                    url = getClass().getResource(urlString);
                } else {
                    url = new URL(urlString);
                }
                URLConnection connection = url.openConnection();
                makeFileNode(node, connection);
            } catch (IOException ioe) {
                System.err.println(ioe);
            }
        }
    }

    private void loadProperty(Node parent, String name,
            StringTokenizer config) throws RepositoryException {
        String typeName;
        if (config.hasMoreTokens()) {
            typeName = config.nextToken();
        } else {
            typeName = "";
        }
        int type;
        try {
            type = PropertyType.valueFromName(typeName);
        } catch (IllegalArgumentException iae) {
            type = PropertyType.STRING;
        }

        String stringValue = ""; // default value
        if (config.hasMoreTokens()) {
            stringValue = config.nextToken();
        }

        /* Property prop = */ parent.setProperty(name, stringValue, type);
    }

    static void makeFileNode(Node node, URLConnection content)
            throws RepositoryException {

        Node contentNode = node.addNode("jcr:content", "nt:resource");
        InputStream ins = null;
        try {
            ins = content.getInputStream();
            contentNode.setProperty("jcr:data", ins);
        } catch (IOException ioe) {
            // ignore, but redefine content data
            contentNode.setProperty("jcr:data", "mockdata", PropertyType.BINARY);
        } finally {
            if (ins != null) {
                try {
                    ins.close();
                } catch (IOException ioe) {}
            }
        }

        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(content.getLastModified());
        contentNode.setProperty("jcr:lastModified", cal);

        String mimeType = content.getContentType();
        if (mimeType == null || mimeType.toLowerCase().indexOf("unknown") >= 0) {
            mimeType = URLConnection.guessContentTypeFromName(node.getName());
        }
        if (mimeType == null) {
            mimeType = "application/octet-stream";
        }
        contentNode.setProperty("jcr:mimeType", mimeType);

        String encoding = content.getContentEncoding();
        if (encoding != null) {
            contentNode.setProperty("jcr:encoding", encoding);
        }
    }

    static Node getParent(Session session, String path)
            throws RepositoryException {

        int lastSlash = path.lastIndexOf('/');
        if (lastSlash < 0) {
            return null;
        }

        String parentPath = path.substring(0, lastSlash);
        if (parentPath.length() == 0) {
            return session.getRootNode();
        }

        try {
            Item item = session.getItem(parentPath);
            if (item.isNode()) {
                return (Node) item;
            }
        } catch (PathNotFoundException pnfe) {

            // create the intermediate node as an unstructured node
            Node parent = getParent(session, parentPath);
            if (parent != null) {
                lastSlash = parentPath.lastIndexOf('/');
                if (lastSlash < 0) {
                    return null;
                }
                String name = parentPath.substring(lastSlash+1);

                return parent.addNode(name, "nt:folder");
            }
        }

        return null;
    }

    private String getName(String path) {
        return path.substring(path.lastIndexOf('/')+1);
    }

    private String loadConvert(String theString) {
        char aChar;
        int len = theString.length();
        StringBuffer outBuffer = new StringBuffer(len);

        for (int x=0; x<len; ) {
            aChar = theString.charAt(x++);
            if (aChar == '\\') {
                aChar = theString.charAt(x++);
                if (aChar == 'u') {
                    // Read the xxxx
                    int value=0;
            for (int i=0; i<4; i++) {
                aChar = theString.charAt(x++);
                switch (aChar) {
                  case '0': case '1': case '2': case '3': case '4':
                  case '5': case '6': case '7': case '8': case '9':
                     value = (value << 4) + aChar - '0';
                 break;
              case 'a': case 'b': case 'c':
                          case 'd': case 'e': case 'f':
                 value = (value << 4) + 10 + aChar - 'a';
                 break;
              case 'A': case 'B': case 'C':
                          case 'D': case 'E': case 'F':
                 value = (value << 4) + 10 + aChar - 'A';
                 break;
              default:
                              throw new IllegalArgumentException(
                                           "Malformed \\uxxxx encoding.");
                        }
                    }
                    outBuffer.append((char)value);
                } else {
                    if (aChar == 't') aChar = '\t';
                    else if (aChar == 'r') aChar = '\r';
                    else if (aChar == 'n') aChar = '\n';
                    else if (aChar == 'f') aChar = '\f';
                    outBuffer.append(aChar);
                }
            } else
                outBuffer.append(aChar);
        }
        return outBuffer.toString();
    }
}
